/**
*  Copyright (c) 2011, Alex Theodoridis
*  All rights reserved.

*  Redistribution and use in source and binary forms, with 
*  or without modification, are permitted provided that the 
*  following conditions are met:
*  Redistributions of source code must retain the above 
*  copyright notice, this list of conditions and the following disclaimer.
*  Redistributions in binary form must reproduce the above 
*  copyright notice, this list of conditions and the following
*  disclaimer in the documentation and/or other materials 
*  provided with the distribution.

*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
*  AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
*  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
*  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
*  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
*  OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
*  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
*  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
*  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
*  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
*  ANY WAY OUT OF THE USE OF THIS SOFTWARE,
*  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
*/

#ifndef XMLPERCEPTRONINSTREAM_H
#define XMLPERCEPTRONINSTREAM_H
#include <NeuralNetwork/Serialization/IPerceptronStream.h>
#include <NeuralNetworkXmlSerializer/IXmlVarStream.h>
#include <NeuralNetworkXmlSerializer/XmlNeuralLayerStream.h>
#include <Utilities/SmartPtr/SharedPtr.h>
#include <tinyxml/tinyxml.h>
#include <NeuralNetwork/Perceptron/PerceptronNeuralNetwork.h>
#include <NeuralNetwork/Serialization/PerceptronMemento.h>
#include <sstream>

struct stat;
namespace nn {

    template<class Var>
    class XmlPerceptronStream : public IPerceptronStream<Var> {
    public:
        static const std::string CONST_TNNLIB_NAMESPACE;
        static const std::string CONST_TNNLIB_NAMESPACE_URL;
        static const std::string CONST_PERCEPTRON_NODE_NAME;
        static const std::string CONST_ENCODING_FORMAT;

    private:
        PerceptronMemento<Var> _memento;
        utils::SharedPtr<IXmlVarStream<Var> > _varStream;

    private:
        bool init(TiXmlElement* rootNode, const utils::SharedPtr<IXmlVarStream<Var> >& varStream){
            bool result=false;
            std::map< unsigned int, NeuralLayerMemento<Var> > neuralLayers;
            std::string rootName = rootNode->Value();
            if( rootNode != NULL && CONST_PERCEPTRON_NODE_NAME == rootNode->Value() && varStream != NULL ){
                TiXmlElement* layerNode=rootNode->FirstChildElement( XmlNeuralLayerStream<Var>::CONST_NEURAL_LAYER_NODE_NAME.c_str() );
                while( layerNode != NULL ){
                    XmlNeuralLayerStream<Var> stream( layerNode, varStream );
                    neuralLayers.insert( std::pair< unsigned int, NeuralLayerMemento<Var> >(neuralLayers.size(), stream.getMemento() ) );
                    layerNode=layerNode->NextSiblingElement( XmlNeuralLayerStream<Var>::CONST_NEURAL_LAYER_NODE_NAME.c_str() );
                }

                layerNode = rootNode->FirstChildElement( XmlNeuralLayerStream<Var>::CONST_OUTPUT_LAYER_NODE_NAME.c_str() );
		XmlNeuralLayerStream<Var> stream( layerNode, varStream );
		_memento.setOutputLayer( stream.getMemento() );
                if ( !neuralLayers.empty() ){
                    _memento.setLayers(neuralLayers);
                    _varStream = varStream;
                    result=true;
                }
            }

            return result;
        }

    public:
        XmlPerceptronStream(const PerceptronNeuralNetwork<Var>& perceptron, const utils::SharedPtr<IXmlVarStream<Var> >& varStream){
            if (varStream == NULL) {
                throw bad_stream("Wrong argument varStream==NULL", __FILE__, __LINE__);
            }

            _memento=perceptron.getMemento();
            _varStream = varStream;
        }


        XmlPerceptronStream(const std::stringstream& xmlStringStream, const utils::SharedPtr<IXmlVarStream<Var> >& varStream){
            TiXmlDocument document;
            std::string xmlString = xmlStringStream.str();
            if( document.Parse(xmlString.c_str() ) == NULL ){
                throw bad_stream("Wrong argument xmlStringStream", __FILE__, __LINE__);
            }

            TiXmlElement* perceptronNode = document.RootElement();
            if( ! init(perceptronNode, varStream) ){
                throw bad_stream("Wrong argument xmlStringStream", __FILE__, __LINE__);
            }
        }


        XmlPerceptronStream(TiXmlElement* rootNode, const utils::SharedPtr<IXmlVarStream<Var> >& varStream){
            if( rootNode != NULL && CONST_PERCEPTRON_NODE_NAME != rootNode->Value() ){

            }

            if( ! init(rootNode, varStream) ){
                throw bad_stream("Wrong argument xmlStringStream", __FILE__, __LINE__);
            }
        }


        const PerceptronNeuralNetwork<Var>& operator >> ( PerceptronNeuralNetwork<Var>& perceptron ){
            perceptron.setMemento(_memento);
            return perceptron;
        }

        const std::string& operator >> (std::string& data) const {
            TiXmlPrinter printer;
            printer.SetIndent(" ");
            TiXmlDocument xmlDocument;
            TiXmlElement* perceptronNode = new TiXmlElement(CONST_PERCEPTRON_NODE_NAME.c_str());
            xmlDocument.LinkEndChild(perceptronNode);
            typename std::map< unsigned int, NeuralLayerMemento<Var> > layers = _memento.getLayers();
            typename std::map< unsigned int, NeuralLayerMemento<Var> >::const_iterator curLayer;
            for( curLayer = layers.begin(); curLayer != layers.end(); curLayer++ ){
                TiXmlDocument layerDocument;
                XmlNeuralLayerStream<Var> stream(curLayer->second, _varStream);
                stream >> layerDocument;
                TiXmlNode* node = layerDocument.FirstChildElement( XmlNeuralLayerStream<Var>::CONST_NEURAL_LAYER_NODE_NAME.c_str() );
                if ( node != NULL ){
                    perceptronNode->LinkEndChild( node->Clone() );
                }
            }
            
            TiXmlDocument layerDocument;
	    XmlNeuralLayerStream<Var> stream(_memento.getOutputLayer(), _varStream, true);
	    stream >> layerDocument;
	    TiXmlNode* node = layerDocument.FirstChildElement( XmlNeuralLayerStream<Var>::CONST_OUTPUT_LAYER_NODE_NAME.c_str() );
	    if ( node != NULL ){
		perceptronNode->LinkEndChild( node->Clone() );
	    }

            xmlDocument.Accept(&printer);
            data = printer.CStr();
            return data;
        }


        const PerceptronMemento<Var>& getMemento()const{
            return _memento;
        }


        PerceptronNeuralNetwork<Var>& operator >> (PerceptronNeuralNetwork<Var>& perceptron) const {
            perceptron.setMemento(_memento);
            return perceptron;
        }


        virtual ~XmlPerceptronStream() {
        }
    };


    template<class Var>
    const std::string XmlPerceptronStream<Var>::CONST_TNNLIB_NAMESPACE = "tnnlib";

    template<class Var>
    const std::string XmlPerceptronStream<Var>::CONST_TNNLIB_NAMESPACE_URL = "http://code.google.com/p/tnnlib";

    template<class Var>
    const std::string XmlPerceptronStream<Var>::CONST_PERCEPTRON_NODE_NAME = "Perceptron";

    template<class Var>
    const std::string XmlPerceptronStream<Var>::CONST_ENCODING_FORMAT = "UTF-8";

}

#endif // XMLPERCEPTRONINSTREAM_H
